﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Web.Script.Serialization;
using KsWare.Breconium.Core.ProtocollData;

namespace KsWare.Breconium.Core {

	internal class Worker:TraceableObject {
		private static object StaticSyncRoot=new object();
		private static int sLastId;
		
		private readonly BreconiumServer mServer;
//		private readonly Session mSession;
		private Thread mThread;
		private int mId;
		private HttpListenerContext mContext;
		private volatile WorkerState mState;
		private MessageData mMessage;


		public Worker(BreconiumServer server, HttpListenerContext context) {
			lock (StaticSyncRoot) {mId = ++sLastId;}
//			mSession = null;
			mState   = WorkerState.None;
			mServer  = server;
			mContext = context;
		}
//		public Worker(Session session, HttpListenerContext context) {
//			mSession = session;
//			mState=WorkerState.StartRequested;
//			mServer = session.Server;
//			mId = ++sLastWorkerId;
//			
//			mThread = new Thread(WorkerProc);
//			mThread.Start(context);
//		}

		public int Id { get { return mId; } }
		public BreconiumServer Server { get { return mServer; } }
		public WorkerState State { get { return mState; }}

		public void Start() { 
			Trace("Start()");
			if(mState!=WorkerState.None) throw new InvalidOperationException();
			mState=WorkerState.StartRequested;Trace("State",mState);
			mThread = new Thread(WorkerProc);
			mThread.Name = "Worker#" + mId;
			Trace("ManagedThreadId",mThread.ManagedThreadId);
			mThread.Start();
		}

		public void Stop() { 
			Trace("Start()");
			if(mState==WorkerState.Stopped) return;
			if(mThread==null) return;
			mState=WorkerState.StopRequested;
			mThread.Abort("Stop requested");
		}

		private void WorkerProc(object o) {
			var tt = "";
			mState=WorkerState.Started;
			Trace("State",mState);

			try {
				HttpListenerRequest request = mContext.Request;
/*#IF(TRACE)*/	tt="WorkerProc: request received: "+request.HttpMethod+" "+request.RawUrl+" "+request.Headers["Content-Type"];
	//			Lib.Trace("Content-Type: "+ request.Headers["Content-Type"]);
/*#IF(TRACE)*/	Trace(tt);Lib.Trace(tt);
				if(!IsJson(request)) {
/*#IF(TRACE)*/		tt="WorkerProc: ERROR Unknown protocol!";Trace(tt);Lib.Trace(tt);
					ResponseText(mContext, "ERROR Unknown protocol!");
					return;
				}
				var identicator = (request.RawUrl??"").ToLower().TrimStart('/');
				switch (identicator) {
					case "init"     : ClientInit       (mContext);break;
					case "command"  : ClientCommand(mContext);break;
					case "pushdata" : ClientPushData   (mContext);break;
					default         : {
/*#IF(TRACE)*/			tt="WorkerProc: ERROR Unknown url!";Trace(tt);Lib.Trace(tt);
						ResponseError(mContext, "Unkown url");
						break;
					}
				}
			} catch (Exception ex) {
				tt = "WorkerProc: ERROR " + ex.GetType().Name + " " + ex.Message;
				Lib.Trace(tt);Trace(tt);
			} finally {
				OnStopped();
			}
		}

		private void OnStopped() { 
			Trace("OnStopped()");
			mState  = WorkerState.Stopped;
			mThread = null;
			mServer.WorkerOnFinished(this);
		}

		private void ClientInit(HttpListenerContext context) {
			Lib.Trace("ClientInit");
			HttpListenerRequest request = context.Request;
			var dynObj=ReadJson(request.InputStream);
			var location = dynObj.String("DATA.Location");
			var mid      = dynObj.Long  ("MID",0);
			var session=new Session(mServer) {Location = location};
			Server.RegisterSession(session);
			ResponseOK(context, session.Id, mid);
		}

		private void ClientPushData(HttpListenerContext context) {
			HttpListenerRequest request = context.Request;
			var json=ReadJson(request.InputStream);
			var sessionId = json.String("SID");
			Lib.Trace(DateTime.Now.ToString("HH:mm:ss,fff")+ " player.name: "+json.String("DATA.unsafeWindow.seed.player.name"));
			ResponseOK(context, sessionId, 0);
		}

		private void ClientCommand(HttpListenerContext context) {
			var t = "";
			HttpListenerRequest request = context.Request;
			var message= ReadMessage(request);
			mMessage = message;

			if(!Server.Sessions.ContainsKey(message.SID)) {
				t="ClientCommandPoll: Session not found. Command ignored. MT:"+message.MT+", SID:"+message.SID;
				Lib.Trace(t);Trace(t);
				return;
			}
			var session   = Server.Sessions[message.SID];

			switch (message.MT) {
				case "COMMAND_POLL":
					t="ClientCommandPoll: COMMAND_POLL";
					Lib.Trace(t);Trace(t);
					break;
				case "COMMAND_ACK": {
					t="ClientCommandPoll: COMMAND_ACK";
					Lib.Trace(t);Trace(t);
					Command command = session.PeekCommand(2, message.MID, TimeSpan.Zero, false);
					command.State=CommandState.S2b_Acknowledged;
					if(message.CH!="0") {
						t="ClientCommandPoll: COMMAND_ACK: ERROR Unexpected channel! CH='"+message.CH+"'";
						Lib.Trace(t);Trace(t);
					}
					break;
				}
				case "COMMAND_RESP": {
					t="ClientCommandPoll: COMMAND_RESP";
					Lib.Trace(t);Trace(t);
					Command command = session.DequeueCommand(2, message.MID, TimeSpan.Zero, false);
					if(command!=null) {
						command.State=CommandState.S3_ResponseReceived;
						if(message.DATA!=null) {
							var data = new Json(message.DATA);
							command.HasError= data.Bool("Success")!=true;
							command.Result  = data.Path("ReturnValue");
							command.Error   = data.Path("Error");
						}
						session.EnqueueCommand(3,command);
					} else {
						t="ClientCommandPoll: COMMAND_RESP: ERROR Command not found! MID='"+message.MID+"'";
						Lib.Trace(t);Trace(t);
					}
					break;
				}
				default: {
					t = "ClientCommandPoll: ERROR Unexpected message type! MT:" + message.MT + "";
					Lib.Trace(t);Trace(t);
					break;
				}
			}

			if(message.CH!="0") {
				ResponseOK(context,message.SID,message.MID);
				return;
			}

			Command cmd1 = session.DequeueCommand(1, null, TimeSpan.FromSeconds(30),false);
			if(cmd1!=null) {
				//cmd1.SessionId = sessionId;
				//cmd1.MessageId = session.NewMessageId();
				session.EnqueueCommand(2, cmd1);
				ResponseCommand(context, cmd1);				
			} else {
				cmd1=new Command("nop"){Message=new MessageData {SID = message.SID,MID = 0}};
				ResponseCommand(context, cmd1);	
			}
		}

		private void ResponseCommand(HttpListenerContext context, Command command) {
			var respData = new MessageData {
				Status = "OK",
				SID    = command.Message.SID,
				MID    = command.Message.MID,
				MT     ="COMMAND_REQ",
				CH     ="",//TODO
				DT     = "CommandData",
				DATA   = new CommandReqData(command)
			};
			
			WriteJson(context, respData);
			mState=WorkerState.ResponseContentSent; Trace("State",mState);
		}

		private void ResponseText(HttpListenerContext context,string s) {
			var response = context.Response;
			response.ContentType = "text/blank; charset=UTF-8";
			string responseString = s;
			byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
			response.ContentLength64 = buffer.Length;
			System.IO.Stream output = response.OutputStream;
			output.Write(buffer,0,buffer.Length);
			output.Close();
			mState=WorkerState.ResponseContentSent;Trace("State",mState);
		}

		private void ResponseOK(HttpListenerContext context, string sessionId, long messageId) {
			var response = context.Response;
			response.ContentType = "application/json; charset=UTF-8";
			string responseString = "{\"Status\":\"OK\",\"SID\":\""+sessionId+"\",\"MID\":\""+messageId+"\"}";
			byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
			response.ContentLength64 = buffer.Length;
			System.IO.Stream output = response.OutputStream;
			output.Write(buffer,0,buffer.Length);
			output.Close();
			mState=WorkerState.ResponseContentSent;Trace("State",mState);
		}

		private void ResponseError(HttpListenerContext context, string reason) {
			var response = context.Response;
			response.ContentType = "application/json; charset=UTF-8";
			string responseString = "{\"Status\":\"ERR\",\"Reason\":\""+reason+"\"}";
			byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
			// Get a response stream and write the response to it.
			response.ContentLength64 = buffer.Length;
			System.IO.Stream output = response.OutputStream;
			output.Write(buffer,0,buffer.Length);
			output.Close();
			mState=WorkerState.ResponseContentSent;Trace("State",mState);
		}

		private void ReadText(Stream inputStream) { 
			var reader=new StreamReader(inputStream);
			var s=reader.ReadToEnd();
			reader.Close();
			mState=WorkerState.RequestContentRead;Trace("State",mState);
		}

		private MessageData ReadMessage(HttpListenerRequest request) { 
			var reader=new StreamReader(request.InputStream);
			var s=reader.ReadToEnd();
			reader.Close();
			mState=WorkerState.RequestContentRead;Trace("State",mState);

			var j = Json.ToJsonObject(s);
			var message=new MessageData {
				NativeJson=s,
				Status= j.String("Status"),
				SID   = j.String("SID"   ),
				MID   = j.Long  ("MID"   ,0),
				CH    = j.String("CH"    ),
				MT    = j.String("MT"    ),
				DT    = j.String("DT"    ),
				DATA  = j.Path  ("DATA"  ),	
			};
			mState=WorkerState.RequestContentRead;
			return message;
		}

		private bool IsJson(HttpListenerRequest request) { 
			if(request.Headers["Content-Type"]==null) return false;
			if(request.Headers["Content-Type"].StartsWith("application/json")) return true;
			if(request.Headers["Content-Type"].StartsWith("text/x-json")) return true;
			return false;
		}

		private Json ReadJson(Stream inputStream) { 
			var reader=new StreamReader(inputStream);
			var s=reader.ReadToEnd();
			reader.Close();
			mState=WorkerState.RequestContentRead;Trace("State",mState);

			var jsonObj = Json.ToJsonObject(s);
			return jsonObj;
		}

		private void WriteJson(HttpListenerContext context, MessageData respData) { 
			string responseString = respData.ToJson();
			var response = context.Response;
			response.ContentType = "application/json; charset=UTF-8";
			byte[] buffer = Encoding.UTF8.GetBytes(responseString);
			response.ContentLength64 = buffer.Length;
			Stream output = response.OutputStream;
			output.Write(buffer,0,buffer.Length);
			output.Close();
		}


		public override string ToString() {
			//return string.Format("{0}#{1}", this.GetType().Name, mId);
			var vm = "";
			if(mMessage!=null) vm = string.Format("MID:{0} CH:{1}", mMessage.MID, mMessage.CH);
			return string.Format("{0}#{1} {2}", this.GetType().Name, mId, vm);
		}
	}

	internal enum WorkerState {
		None,
		StartRequested,
		Started,
		RequestContentRead,
		ResponseContentSent,
		StopRequested,
		Stopped,
	}

	internal enum MessageType{None,COMMAND_POLL,COMMAND_ACK,COMMAND_RESP,COMMAND_REQ}
	public enum CommandName{None,nop}
}
